package com.heima.wemedia.service.impl;


import com.alibaba.fastjson.JSON;
import com.heima.common.Constants;
import com.heima.common.aliyun.GreenImageUploadScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.aliyun.util.AuditResultDTO;
import com.heima.common.delayTask.RedisDelayedQueueUtil;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.common.threadlocal.UserThreadLocalUtils;
import com.heima.common.util.BeanHelper;
import com.heima.common.util.SensitiveWordUtil;
import com.heima.feign.ArticleFeign;
import com.heima.file.service.MinioService;
import com.heima.model.media.dtos.WmNewsContentItem;
import com.heima.model.media.dtos.WmNewsDto;
import com.heima.model.media.dtos.WmNewsResultDTO;
import com.heima.wemedia.entity.WmNews;
import com.heima.wemedia.entity.WmSensitive;
import com.heima.wemedia.service.WmAuditService;
import com.heima.wemedia.service.WmNewsService;
import com.heima.wemedia.service.WmSensitiveService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.redisson.api.RBlockingDeque;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.heima.common.Constants.THREAD_POOL_NAME;

@Slf4j
@Service
public class WmAuditService2Impl implements WmAuditService {

    @Autowired
    private GreenTextScan textAudit;

    @Autowired
    private GreenImageUploadScan imageAudit;

    @Autowired
    @Lazy //解决循环依赖
    private WmNewsService newsService;

    @Autowired
    MinioService minioService;

    @Autowired
    private WmSensitiveService sensitiveService;

    @Autowired
    private Tess4jClient tess4jClient;

    @Autowired
    private ArticleFeign articleFeign;

    @Value("${aliyun.audit.image.enable:false}")
    private Boolean imageAuditEnable;

    @Value("${aliyun.audit.text.enable:false}")
    private Boolean textAuditEnable;

    @Value("${sensitive.words.audit.enable:false}")
    private Boolean sensitiveWordsAudit;

    @Value("${image.sensitive.words.audit.enable:false}")
    private Boolean imageSensitiveWordsAudit;

    @Autowired
    RedisDelayedQueueUtil redisDelayedQueueUtil;

    private ThreadPoolExecutor taskMsgThreadPool = new ThreadPoolExecutor(1, 1,
            120, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    private ThreadPoolExecutor bizThreadPool  = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors()*2,
            120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));

    @PostConstruct
    public void postConstruct(){
        // 子线程，while(true)不停的从延迟队列中取消息
        taskMsgThreadPool.submit(new Runnable() {
            @Override
            public void run() {
                while(true){
                    // 从延迟队列中取消息
                    try{
                        Integer wmNewsId = redisDelayedQueueUtil.getDelayQueue(Constants.DELAY_QUEUE_NAME);
                        bizThreadPool.submit(new Runnable() {
                            @Override
                            public void run() {
                                // 做文章发布
                               publishArticle(wmNewsId);
                            }
                        });
                    }catch(Exception e){
                        log.error("从阻塞队列获取消息异常:{}", ExceptionUtils.getStackTrace(e));
                    }
                }
            }
        });
    }

    private void publishArticle(Integer wmNewsId){
        // 根据自媒体文章id查询自媒体文章详细数据
        WmNews newsDB = newsService.getById(wmNewsId);
        // 发布文章，把自媒体文章发布成article文章，feign
        WmNewsResultDTO createArticleDto = BeanHelper.copyProperties(newsDB, WmNewsResultDTO.class);
        createArticleDto.setChannelName("XXXX");// 根据channleId查询
        Long articleId = articleFeign.createArticle(createArticleDto);
        // 修改状态为已经发布 回填articleId到wm_news中，并且 修改状态已经上架
        updateWmNewsStatus(wmNewsId, 9, "", articleId, true);
    }

    @Override
    @Async(THREAD_POOL_NAME)
    //TODO @GlobalTransactional(rollbackFor = Exception.class)
    public void asyncAudit(Integer wmUserId, WmNewsDto dto) {
        log.info("开始异步审核。。。。。");
        // 审核，阿里云的文本审核+阿里云图片审核+敏感词审核+图片敏感词审核
        boolean auditResult = audit(dto);
        if(!auditResult){
            return;
        }
        // 审核通过， 改状态， 发布文章
        log.info("--------------------->审核通过");
        long publishMills = dto.getPublishTime()==null?System.currentTimeMillis():dto.getPublishTime().getTime();
        long currentMills = System.currentTimeMillis();
        long diff = publishMills - currentMills;
        if(dto.getPublishTime()!= null && diff > 0 ){
            // 做延期发布
            redisDelayedQueueUtil.addQueue(dto.getId(), diff, TimeUnit.MILLISECONDS, Constants.DELAY_QUEUE_NAME);
            // 更改状态为审核通过等待发布
            updateWmNewsStatus(dto.getId(), 8, "");
            return;
        }
        // 发布文章，把自媒体文章发布成article文章，feign
        WmNewsResultDTO createArticleDto = buildCreateArticleDto(wmUserId, dto);
        Long articleId = articleFeign.createArticle(createArticleDto);
        // 修改状态为已经发布 回填articleId到wm_news中，并且 修改状态已经上架
        updateWmNewsStatus(dto.getId(), 9, "", articleId, true);
    }

    private boolean audit(WmNewsDto dto){
        // 解析正文的json数据
        List<WmNewsContentItem> wmNewsContentItems = JSON.parseArray(dto.getContent(), WmNewsContentItem.class);
        // 获取待审核的文本
        String auditText = buildAuditText(wmNewsContentItems, dto);
        // 获取待审核的图片
        List<byte[]> imageByteList = buildAuditImages(wmNewsContentItems, dto);
        // 如果启用了阿里云的文本审核
        if(textAuditEnable){
            log.info("--------------------->阿里云文本审核");
            // 阿里云文本审核
            boolean result = aliyunTextAudit(auditText, dto);
            if(!result){
                return false;
            }
        }
        if(imageAuditEnable){
            //阿里云图片审核, 解析正文和封面中的图片地址，从minio下载得到文件的字节数组
            log.info("--------------------->阿里云图片审核");
            boolean result = aliyunImageAudit(imageByteList, dto);
            if(!result){
                return false;
            }
        }
        if(sensitiveWordsAudit || imageSensitiveWordsAudit){
            // 获取敏感词的列表
            List<String> senesitiveWords = buildSensitiveWords();
            if(sensitiveWordsAudit){
                // 文本敏感词审核
                log.info("--------------------->文本敏感词审核");
                boolean result = sensitiveWordsAudit(auditText, dto, senesitiveWords);
                if(!result){
                    return false;
                }
            }
            if(imageSensitiveWordsAudit){
                // 图片敏感词审核
                log.info("--------------------->图片敏感词审核");
                boolean result = imageSensitiveWordsAudit(imageByteList, dto, senesitiveWords);
                if(!result){
                    return false;
                }
            }
        }
        return true;
    }

    private WmNewsResultDTO buildCreateArticleDto(Integer wmUserId, WmNewsDto dto) {
        WmNewsResultDTO result = BeanHelper.copyProperties(dto, WmNewsResultDTO.class);
        result.setWmUserId(wmUserId);
        result.setChannelName("123");//TODO ,根据channelId查询channelName
        result.setImages(CollectionUtils.isEmpty(dto.getImages())?"":dto.getImages().stream().collect(Collectors.joining(",")));
        if(dto.getPublishTime() == null){
            result.setPublishTime(new Date());
        }
        return result;
    }

    private List<String> buildSensitiveWords() {
        // 把数据库中所有的敏感词都查询出来，feign调用 TODO
        List<WmSensitive> sensitiveList = sensitiveService.list();
        if(!CollectionUtils.isEmpty(sensitiveList)) {
            return sensitiveList.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
        }
        return null;
    }

    private boolean sensitiveWordsAudit(String auditText, WmNewsDto dto, List<String> senesitiveWords) {
        if(CollectionUtils.isEmpty(senesitiveWords)){
            return true;
        }
        Map<String, Integer> resultMap = SensitiveWordUtil.matchWords(senesitiveWords, auditText);
        if(resultMap.size() > 0){
            // 说明敏感词审核不通过，改状态，填充失败的理由
            updateWmNewsStatus(dto.getId(), 2, resultMap.keySet().stream().collect(Collectors.joining(",")));
            return false;
        }
        return true;
    }

    private boolean imageSensitiveWordsAudit(List<byte[]> imageByteList, WmNewsDto dto, List<String> senesitiveWords) {
        // 图片文字识别，敏感词审核, 把图片中的文本提取出来，做文本的敏感词审核
        if(CollectionUtils.isEmpty(imageByteList) || CollectionUtils.isEmpty(senesitiveWords)){
            return true;
        }
        try {
            for(byte[] imageBytes : imageByteList){
                BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
                String imageText = tess4jClient.doOCR(bufferedImage);
                Map<String, Integer> resultMap = SensitiveWordUtil.matchWords(senesitiveWords, imageText);
                if(!CollectionUtils.isEmpty(resultMap)){
                    // 说明敏感词审核不通过，改状态，填充失败的理由
                    updateWmNewsStatus(dto.getId(), 2, resultMap.keySet().stream().collect(Collectors.joining(",")));
                    return false;
                }
            }
        } catch (Exception e) {
            log.error("图片敏感词审核异常：{}", ExceptionUtils.getStackTrace(e));
            return false;
        }
        return true;
    }

    private boolean aliyunImageAudit(List<byte[]> imageByteList, WmNewsDto dto) {
        if(CollectionUtils.isEmpty(imageByteList)){
           return true;
        }
        try {
            AuditResultDTO auditResultDTO = imageAudit.imageScan(imageByteList);
            if(auditResultDTO.isAuditFail()){
                // 审核失败,改状态，填充失败的理由
                updateWmNewsStatus(dto.getId(), 2, auditResultDTO.getReason());
                return false;
            }else if(auditResultDTO.isAuditReview()){
                // 审核不确定，转人工
                updateWmNewsStatus(dto.getId(), 3, auditResultDTO.getReason());
                return false;
            }
        } catch (Exception e) {
            log.error("阿里云图片审核异常：{}", ExceptionUtils.getStackTrace(e));
            return false;
        }
        return true;
    }

    private boolean aliyunTextAudit(String auditText, WmNewsDto dto) {
        try {
            AuditResultDTO auditResultDTO = textAudit.greenTextScan(auditText);
            if(auditResultDTO.isAuditFail()){
                // 审核失败,改状态，填充失败的理由
                updateWmNewsStatus(dto.getId(), 2, auditResultDTO.getReason());
                return false;
            }else if(auditResultDTO.isAuditReview()){
                // 审核不确定，转人工
                updateWmNewsStatus(dto.getId(), 3, auditResultDTO.getReason());
                return false;
            }
        } catch (Exception e) {
            log.error("阿里云文本审核异常：{}", ExceptionUtils.getStackTrace(e));
            return false;
        }
        return true;
    }

    private List<byte[]> buildAuditImages(List<WmNewsContentItem> wmNewsContentItems, WmNewsDto dto) {
        // 取正文图
        List<String> contentImageUrlList = wmNewsContentItems.stream().filter(item -> item.getType().equals("image")).map(item -> item.getValue()).collect(Collectors.toList());
        // 取封面图
        List<String> coverImageUrlList = dto.getImages();
        // 正文图可能跟封面图是同一张图，去重一下
        Set<String> noRepeateUrlList = new HashSet<>();
        if(!CollectionUtils.isEmpty(contentImageUrlList)){
            noRepeateUrlList.addAll(contentImageUrlList);
        }
        if(!CollectionUtils.isEmpty(coverImageUrlList)){
            noRepeateUrlList.addAll(coverImageUrlList);
        }
        List<byte[]> imageByteList = new ArrayList<>(noRepeateUrlList.size());
        if(!CollectionUtils.isEmpty(noRepeateUrlList)){
            // 从minio下载
            for(String url : noRepeateUrlList){
                byte[] imageBytes = minioService.downLoadFile(url);
                if(imageBytes != null && imageBytes.length > 0){
                    imageByteList.add(imageBytes);
                }
            }
        }
        return imageByteList;
    }

    private String buildAuditText(List<WmNewsContentItem> wmNewsContentItems, WmNewsDto dto){
        // 标题
        String title = dto.getTitle();
        // 标签
        String label = dto.getLabels();
        //正文中的文本
        String text = wmNewsContentItems.stream().filter(item -> item.getType().equals("text")).map(item -> item.getValue()).collect(Collectors.joining("--"));
        // 待审核的文本： 标题 + 标签 + 正文中的文本
        return title + "--" + label + "--" +text;
    }

    private void updateWmNewsStatus(int wmNewsId, Integer status, String reason){
        updateWmNewsStatus(wmNewsId,status,reason,-1L, null);
    }

    private void updateWmNewsStatus(int wmNewsId, Integer status, String reason, Long articleId, Boolean enable){
        WmNews toBeUpdate = new WmNews();
        toBeUpdate.setId(wmNewsId);
        toBeUpdate.setStatus(status);
        toBeUpdate.setReason(reason);
        if(articleId > 0){
            toBeUpdate.setArticleId(articleId);
        }
        if(enable != null){
            // 更新上下架的状态
            toBeUpdate.setEnable(enable);
        }
        newsService.updateById(toBeUpdate);
    }
}
